<?php
/**
 * uploadfile.class.php
 * 通用上传类
 */
class uploadfile
{
    protected $config = array(
        'index'
    );

    /* 上传参数 */
    private $_updSliceSize = '';
    private $sliceDir = '';

    /**
     * 初始化
     */
    public function init ($method)
    {
        /* 过滤可执行方法 */
        if (! method_exists($this, $method)) {
            throw new MgException('method ' . $method . ' not exists', 10001);
        }
        if (! in_array($method, $this->config)) {
            throw new MgException('method not allow to access', 10002);
        }

        $this->_updSliceSize = Mg()->config['upload_slice_size'];
        $this->sliceDir = Mg()->config['upload_tmp_dir'];

        /* 执行方法 */
        $this->$method();
    }

    /**
     * 上传分片入口
     */
    public function index()
    {
        /* 设置脚本超时时间 */
        set_time_limit(600);

        /* 允许跨域 */
        header("Access-Control-Allow-Origin: *");
        header("Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS");
        header("Access-Control-Max-Age: 3628800");
        header("Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type");

        if (! isset($_SERVER['REQUEST_METHOD']) || ($_SERVER['REQUEST_METHOD'] != 'POST' &&
                $_SERVER['REQUEST_METHOD'] != 'OPTIONS')) {
            throw new MgException('request method not allow', 10018);
        }

        /* 参数验证 */
        $this->_validate();

        /* MongoDB 获取上传信息 */
        $taskinfo = $this->_getfileinfomongo();

        /* 分片秒传 */
        $res = $this->isUploaded($taskinfo);

        if (! $res) {
            /* 检测分片完整性 */
            $sliceStream = $this->_checkslice($taskinfo);

            /* 写文件 */
            $wResult = $this->writeslice($taskinfo, $sliceStream);

            /* 保存分片 */
            $res = $this->_saveSliceBase64($taskinfo);
        }

        if ($res) {
            $this->update_content_status($taskinfo);
            Mg()->response->status = 0;
            Mg()->response->msg = 'slice upload success';
        } else {
            Mg()->response->status = 500;
            Mg()->response->msg = 'server error';
        }
    }

    /**
     * 参数校验
     */
    private function _validate ()
    {
        /* 记录请求参数 */
        Mg()->log->log([
            'logdesc' => 'request_data',
            'get' => $_GET,
            'post' => $_POST,
            'files' => $_FILES
        ], 0, 'upload_files_');

        /* 检查任务ID是否存在 */
        if (! isset($_GET['order_id']) || ! preg_match('/^[0-9a-zA-Z]+$/', $_GET['order_id'])) {
            throw new MgException('order_id not exists or format error', 10003);
        }

        /* 检查当前传输分片 */
        if (! isset($_GET['block']) || ! is_numeric($_GET['block'])) {
            throw new MgException('block not exists or format error', 10005);
        }

        /* 检查当前传输分片md5值 */
        if (! isset($_GET['blockmd5']) || ! preg_match('/^[0-9a-zA-Z]{32}$/', $_GET['blockmd5'])) {
            throw new MgException('blockmd5 not exists or format error', 10006);
        }

        /* 检查sliceCount值 */
        if (! isset($_GET['sliceCount']) || ! preg_match('/^[0-9]+$/', $_GET['sliceCount'])) {
            throw new MgException('sliceCount not exists or format error', 10033);
        }
    }

    /**
     * 获取上传信息mongo
     */
    private function _getfileinfomongo ()
    {
        $collection = Mg()->mongo->collection('task_order');
        $uploadfile = $collection->findOne(function  ($query)
        {
            $query->whereId($_GET['order_id']);
        });
        if (empty($uploadfile)) {
            throw new MgException('filestaskinfo not exists', 10012);
        }

        return $uploadfile;
    }

    /**
     * 秒传
     */
    private function isUploaded ($taskinfo)
    {
        /* 暂不启用分片秒传@start */
        return false;
        /* 暂不启用分片秒传@end */

        $flag = false;

        $buffer = Mg()->mongo->collection('task_block_buffer');
        $isOrderUploaded = $buffer->findOne(function ($query) use( $taskinfo)
        {
            $query->where([
                'hashcode' => $_GET['blockmd5'],
                'order_id' => $_GET['order_id']
            ]);
        });

        if (empty($isOrderUploaded)) {
            $isUploaded = $buffer->findOne(function ($query) use($taskinfo)
            {
                $query->where([
                    'hashcode' => $_GET['blockmd5']
                ]);
            });

            if (! empty($isUploaded)) {
                $sliceInfo = array(
                    'userid' => (int) $taskinfo['userid'],
                    'order_id' => $_GET['order_id'],
                    'content' => $isUploaded['content'],
                    'hashcode' => $_GET['blockmd5'],
                    'order_block' => $_GET['block'],
                    'is_quote' => 1,
                    'nums_block' => $taskinfo['nums_block'],
                    'lock_to' => '',
                    'status' => 0,
                    'ctime' => time(),
                    'mtime' => time()
                );

                $flag = $buffer->insert($sliceInfo);
            }
        } else {
            $flag = true;
        }

        return $flag;
    }

    /**
     * 读取分片内容$_FILES['name']['tmp_name'];
     * $_GET['order_id'] //任务id
     * $_GET['token'] //token
     * $_GET['blockmd5'] //blockmd5 分片MD5
     * $_GET['block'] //分片片键
     * 文件流写入mongo
     */
    private function _checkslice ($taskinfo)
    {
        $slice = (! empty($_FILES['file']['tmp_name'])) ? $_FILES['file']['tmp_name'] : false;

        if (! $slice) {
            throw new MgException('files not be empty', 10009);
        }

        switch ($_FILES['file']['error']) {
            case 1:
            case 2:
                throw new MgException('upload slice too large', 10013);
                break;
            case 3:
                throw new MgException('upload slice uncomplete', 10014);
                break;
            case 4:
                throw new MgException('upload slice empty', 10015);
                break;
                break;
            case 6:
                throw new MgException('upload tmp dir not find', 10016);
                break;
            case 7:
                throw new MgException('upload tmp dir access denied', 10017);
                break;
        }

        $fp = @fopen($slice, 'rb');
        $sliceStream = fread($fp, $this->_updSliceSize);

        /* 检查分片大小 */
        if ($_GET['block'] != $taskinfo['nums_block']) {
            $updSlice = $this->_updSliceSize;
        } else {
            $tmpSlice = $taskinfo['filesize'] % $this->_updSliceSize;
            $updSlice = $tmpSlice ? $tmpSlice : $this->_updSliceSize;
        }

        $execLen = strlen($sliceStream);
        if ($execLen != $updSlice) {
            Mg()->log->log([
                'mongoSliceSize' => $updSlice,
                'strlenSlice' => $execLen
            ], 0, 'upload_files_');
            throw new MgException('blocksize not matched', 10010);
        }
        /* 检查分片MD5 */
        $execMd5 = md5($sliceStream);
        if ($execMd5 != $_GET['blockmd5']) {
            Mg()->log->log([
                'logdesc' => 'slice md5 verify',
                'execMd5' => $execMd5
            ], 0, 'upload_files_');
            throw new MgException('blockmd5 not match', 10011);
        }

        return $sliceStream;
    }

    /**
     * 写文件
     */
    private function writeslice ($taskinfo, $sliceStream)
    {
        $slicepath = $this->sliceDir . $taskinfo['userid'] . DIRECTORY_SEPARATOR . $_GET['order_id'] . DIRECTORY_SEPARATOR . $_GET['block'] . '.part';
        createdir(dirname($slicepath));
        try {

            $slice = @fopen($slicepath, 'ab+');
            /* if (! flock($slice, LOCK_EX)) { */
            $res = @fwrite($slice, $sliceStream);
            if (! $res) {
                throw new \MgException('slice part write failed', 100035);
            }
            /*
             * } else {
             * throw new \MgException('cant not get the file lock', 10034);
             * }
             */

            flock($slice, LOCK_UN);
            fclose($slice);
            unset($sliceStream);
        } catch (\Exception $e) {
            flock($slice, LOCK_UN);
            fclose($slice);
            unset($sliceStream);
            throw new \MgException($e->getMessage(), $e->getCode());
        }
    }

    /**
     * 组织参数,入MongoDB
     */
    private function _saveSliceBase64 ($taskinfo)
    {
        $slicePath = (int) $taskinfo['userid'] . DIRECTORY_SEPARATOR . $_GET['order_id'] . DIRECTORY_SEPARATOR . $_GET['block'] . '.part';
        $sliceInfo = array(
            'userid' => (int) $taskinfo['userid'],
            'order_id' => $_GET['order_id'],
            'content' => $slicePath,
            'hashcode' => $_GET['blockmd5'],
            'order_block' => $_GET['block'],
            'is_quote' => 0,
            'nums_block' => $taskinfo['nums_block'],
            'lock_to' => $taskinfo['lock_to'],
            'status' => 0,
            'ctime' => time(),
            'mtime' => time()
        );

        $collection = Mg()->mongo->collection('task_block_buffer');

        $res = $collection->insert($sliceInfo);

        Mg()->log->log([
            'logdesc' => 'sliceinfo',
            'sliceinfo' => [
                'order_id' => $_GET['order_id'],
                'hashcode' => $_GET['blockmd5'],
                'userid' => (int) $taskinfo['userid']
            ],
            'mongores' => $res
        ], 0, 'upload_files_mongo_');
        return $res ? true : false;
    }

    private function update_content_status ($taskinfo)
    {
        if ($_GET['sliceCount'] == $_GET['block']) {
            /* 更新附件表信息 */
            $attachment_content = Mg()->mongo->collection('attachment_content_temp');
            $attachment_content->update(function ($query)
            {
                $query->where([
                    'order_id' => $_GET['order_id']
                ])->set('updated_time', formatMicrotime(time() * 1000))->set('status', 6);
            });

            /* 更新任务表 */
            $task_order = Mg()->mongo->collection('task_order');
            $task_order->update(function ($query)
            {
                $query->whereId($_GET['order_id'])
                    ->set('status', 6)
                    ->set('mtime', time());
            });

            /* 更新文件（夹）同名处理表 */
            $attachinfo = $attachment_content->findOne(function ($query)
            {
                $query->where([
                    'order_id' => $_GET['order_id']
                ]);
            });
            self::updateAttachmentCopyNums($taskinfo['userid'], $attachinfo['title'], 1, $taskinfo['folder_hid']);

            /* 修改is_has_children */
            $attachment_folder = Mg()->mongo->collection('attachment_folder');
            $is_has_children = $attachment_folder->update(function  ($query) use( $taskinfo)
            {
                $query->where([
                    'hid' => $taskinfo['folder_hid'],
                    'userid' => $taskinfo['userid']
                ])->set([
                    'is_has_children' => 1
                ]);
            });
        }
    }

    /**
     * 更新复制标识的数字位信息
     *
     * @param integer $userid 用户 ID
     * @param string $title 重命名后的文件（夹）名称
     * @param integer $type 类型。1：文件；2：文件夹；
     * @param string $folder_hid 所属目录的节点序列
     * @return string|boolean
     */
    private function updateAttachmentCopyNums ($userid, $title, $type = 1, $folder_hid = '0')
    {
        if (! isset($userid) || ! $title) {
            return false;
        }

        /* 拆解文件名称为名称和后缀 */
        $suffix = strrchr($title, '.');
        $title_pre = substr($title, 0, strrpos($title, '.'));
        if ($type == 2) {
            $title_pre = $title;
        }

        /* 检查名称是否是以复制性标识结尾，即括弧夹数字的形式 */
        if ($check_copy = preg_match('/(.*)\((\d+)\)$/', $title_pre, $matched)) {
            $title_simple = $matched[1];
            $num_add = $matched[2];
        } else {
            $title_simple = $title_pre;
            $num_add = -1;
        }

        /* 查询该用户下的同名文件信息 */
        $collection_attachment = Mg()->mongo->collection('attachment_juna');
        $where = array(
            'userid' => $userid,
            'type' => $type,
            'folder_hid' => $folder_hid,
            'title_simple' => $title_simple
        );
        if ($type == 1) {
            $where['suffix'] = $suffix;
        }

        $juna_info = $collection_attachment->findOne(function ($query) use( $where)
        {
            $query->where($where);
        });

        if (! empty($juna_info)) {
            $juna_info = $this->std_class_object_to_array($juna_info);

            /* 处理复制标识的数字位记录 */
            $copy_nums_new = $juna_info['copy_nums'] . ',' . $num_add;
            $copy_nums_new = explode(',', $copy_nums_new);
            sort($copy_nums_new); // 排序
            $copy_nums_new = array_unique($copy_nums_new); // 去重
            $copy_nums_new = implode(',', $copy_nums_new);

            /* 执行更新 */
            $set = array(
                'copy_nums' => $copy_nums_new,
                'id' => $juna_info['_id']['$id']
            );
            $collection_attachment->update(function  ($query) use( $set)
            {
                $query->whereId($set['id'])
                    ->set('copy_nums', $set['copy_nums']);
            });
        } else { // 不存在，则执行新增操作
            $set = array(
                'userid' => $userid,
                'type' => $type,
                'folder_hid' => $folder_hid,
                'title_simple' => $title_simple,
                'suffix' => $suffix ? $suffix : '',
                'copy_nums' => $num_add
            );

            $collection_attachment->insert($set);
        }

        return true;
    }

    /**
     * [std_class_object_to_array 将对象转成数组]
     *
     * @param [stdclass] $stdclassobject
     *            [对象]
     * @return [array] [数组]
     */
    protected function std_class_object_to_array ($stdclassobject)
    {
        $_array = is_object($stdclassobject) ? get_object_vars($stdclassobject) : $stdclassobject;
        foreach ($_array as $key => $value) {
            $value = (is_array($value) || is_object($value)) ? $this->std_class_object_to_array($value) : $value;
            $array[$key] = $value;
        }
        return $array;
    }
}
